package govenor.manager;

import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.hp.hpl.jena.rdf.model.Model;

import constants.NetworkConstants;

import networkdescriptor.NetworkDescriptor;

import model.jena.model.GovenorModelBuilder;
import multicast.MultiCastCommsManager;
import tcp.command.CommandClient;
import tcp.heartbeat.HeartBeatManager;
import tcp.heartbeat.HeartbeatLostHandler;
import govenor.overwatch.ClientDescriptor;
import govenor.overwatch.FileStoreNode;
import govenor.overwatch.OverWatcher;
import govenor.replicator.ReplicationService;
import govenor.status.ClientStatusManager;
import govenor.status.FileStoreStatus;
import govenor.status.FileStored;
import govenor.status.StatusManager;
import govenor.updater.GovenorUpdater;
import handlers.DataCallBack;
import handlers.MessageHandler;

public class GovenorManager implements MessageHandler, HeartbeatLostHandler, DataCallBack{
	protected OverWatcher systemWatcher ;
	protected HeartBeatManager hbManager ;
	protected MultiCastCommsManager mcCommsManager ;
	protected StatusManager statusManager;
	protected ClientStatusManager cStatusManager;
	protected ReplicationService replication;
	private HashMap<String,FileStored>mapOfFiles;
	private GovenorUpdater govUpdater;
	
	public GovenorManager() {
		//Instantiate each of the components.
		this.systemWatcher = new OverWatcher();
		this.mapOfFiles = (HashMap<String, FileStored>) this.buildMapOfFilesToHosts();
		this.govUpdater = new GovenorUpdater(this);
	}
	
	public void addHeartbeatManager(){
		//Only create one Heartbeat Manager Instance
		if(this.systemWatcher != null && this.hbManager == null){
			this.hbManager = new HeartBeatManager(this.systemWatcher,this);
		}
	}
	public void addMulticastCommsManager(){
		//Start up the comms manager
		this.mcCommsManager = new MultiCastCommsManager();
		this.mcCommsManager.addMessageHandler(this);
	}
	public void addStatusManager(){
		this.statusManager = new StatusManager(this.systemWatcher);
	}
	public void addClientStatusManager(){
		this.cStatusManager = new ClientStatusManager(this);
	}
	public void addReplicationService(){
		this.replication = new ReplicationService(this);
	}
	public void handleMessage(String message) {
		//Trim the trailing white spaces from the message
		message = message.trim();
		String[] temp = message.split(":");
		for(int i = 0; i < temp.length; i++){
			System.out.println(temp[i]);
		}
		if(temp[0].equalsIgnoreCase("ANNOUNCEFS")){
			this.handleFileStoreAnnouncement(temp);
		}
		if(temp[0].equalsIgnoreCase("CLIENT")){
			this.handleClientJoinRequest(temp);
		}
		if(temp[0].equalsIgnoreCase("DELETE")){
			this.handleFileDeletion(temp);
		}
		if(temp[0].equalsIgnoreCase("UPLOAD")){
			this.handleFileUploadRequest(temp);
		}
		
	}
	private void handleFileUploadRequest(String[] data){
		String storeId = this.getSystemWatcher().findBestFileStore();
		FileStoreNode node = this.getSystemWatcher().getFileStoreNodeFromFileStore(storeId);
		String[] params = new String[2];
		params[0] = node.getFileUploadNetworkDescription().getHostname();
		params[1] = ""+node.getFileUploadNetworkDescription().getPortNumber();
		String portNumber = data[2].substring(3, data[2].length());
		CommandClient.sendCommand(NetworkConstants.COMMAND_ACTION_UPLOAD_FILE, null, data[1], Integer.parseInt(portNumber), null, params);
	}
	private void handleFileStoreAnnouncement(String[] data){
		FileStoreNode fsNode = new FileStoreNode() ;
		String hostname = data[1] ;
		String[] tempData = new String[(data.length-2)];
		System.arraycopy(data, 2, tempData, 0, data.length-2);
		for(int i = 0; i < tempData.length; i++){
			System.out.println(tempData[i]);
			String protocol = tempData[i].substring(0, 2) ;
			String portNumber = tempData[i].substring(3, tempData[i].length());
			if(protocol.equalsIgnoreCase("HB")){
				fsNode.setHeartBeatNetworkDescription(hostname, Integer.parseInt(portNumber));
			}
			if(protocol.equalsIgnoreCase("FU")){
				fsNode.setfileUploadNetworkDescription(hostname, Integer.parseInt(portNumber));
			}
			if(protocol.equalsIgnoreCase("FD")){
				fsNode.setfileDownloadNetworkDescription(hostname, Integer.parseInt(portNumber));
			}
			if(protocol.equalsIgnoreCase("CP")){
				fsNode.setCommandNetworkDescription(hostname, Integer.parseInt(portNumber));
			}
		}
		this.systemWatcher.addWatchedElement(fsNode);
	}
	private void handleClientJoinRequest(String[] data){
		String hostname = data[1];
		String portNumber = null;
		if(data.length == 3){
			String protocol = data[2].substring(0, 2) ;
			if(protocol.equalsIgnoreCase("CP")){
			portNumber = data[2].substring(3, data[2].length());
			this.systemWatcher.registerClient(hostname, Integer.parseInt(portNumber));
			}
		}
		StringWriter writer = new StringWriter();
		Model model = GovenorModelBuilder.buildModel(this.mapOfFiles);
		model.write(writer);
		String[] params = new String[1];
		params[0] = writer.toString();
		//Open the command port on the client and bounce a XML descriptor of the files and the hosts to contact for each file.
		this.sendClientsUpdate(hostname, portNumber, params);
		
	}
	public void sendClientsUpdate(String hostname, String portNumber,String[] params){
		CommandClient.sendCommand(NetworkConstants.COMMAND_ACTION_CLIENT_UPDATE, hostname+":"+portNumber, hostname, Integer.parseInt(portNumber), this, params);
	}
	public void sendClientFileDelete(String hostname, int portNumber, String fileName){
		String[] newArray = new String[1];
		newArray[0] = fileName;
		CommandClient.sendCommand(NetworkConstants.COMMAND_ACTION_FILE_DELETE,null, hostname, portNumber, null, newArray);
	}
	public void sendFileReplicationRequest(String hostHostName,int hostPortNumber, String fileName, String destinationHost, int destinationUploadPort){
		String[] params = new String[3];
		params[0] = fileName;
		params[1] = destinationHost;
		params[2] = ""+destinationUploadPort;
		CommandClient.sendCommand(NetworkConstants.COMMAND_ACTION_REPLICATE, null, hostHostName, hostPortNumber, null, params);
	}
	public void updateDefinitions(){
		this.mapOfFiles = (HashMap<String, FileStored>) this.buildMapOfFilesToHosts();
	}
	private void handleFileDeletion(String[] data){
		//Find hosts of the file
		Map<String,List> mapOfHosts = this.findHostsOfFileForUse(data[1]);
		List<FileStoreNode> listOfHosts = mapOfHosts.get(data[1]);
		for(int i = 0; i < listOfHosts.size(); i++){
			FileStoreNode fsn = listOfHosts.get(i);
			this.sendClientFileDelete(fsn.getCommandNetworkDescription().getHostname(), fsn.getCommandNetworkDescription().getPortNumber(), data[1]);
		}
	}
	public Map<String,FileStored> buildMapOfFilesToHosts(){
		HashMap<String, FileStored> mapOfFilesToHosts = new HashMap<String, FileStored>();
		
		Map<String,String> mapOfFilesToServerIds = this.systemWatcher.getAllFilesStored();
		Set<String> setOfFilenames = mapOfFilesToServerIds.keySet();
		Iterator<String> iter = setOfFilenames.iterator();
		FileStored filesStored = null;
		while(iter.hasNext()){
			String filename = iter.next();
			String serverList = mapOfFilesToServerIds.get(filename);
			String[] temp = serverList.split(":");
			String server1 = temp[0]+":"+temp[1];
			FileStoreStatus statsServer1 = this.systemWatcher.getFileStatusFromFileStore(server1, filename);
			FileStoreStatus statsServer2 = null;
			String server2 = null;
			filesStored = new FileStored(filename);
			if(temp.length > 2){
				server2 = temp[2]+":"+temp[3];
				statsServer2 = this.systemWatcher.getFileStatusFromFileStore(server2, filename);
				
			}
			if(statsServer2 != null){
				if(statsServer1.compare(statsServer2) > 0){
					//Means Stats Server 1 is better use it first
					filesStored.addNetworkDescriptor(this.systemWatcher.getWatchedElement(server1).getFileDownloadNetworkDescription());
					filesStored.addNetworkDescriptor(this.systemWatcher.getWatchedElement(server2).getFileDownloadNetworkDescription());
					
				}else{
					//Means Stats Server 2 is better use it first
					filesStored.addNetworkDescriptor(this.systemWatcher.getWatchedElement(server2).getFileDownloadNetworkDescription());
					filesStored.addNetworkDescriptor(this.systemWatcher.getWatchedElement(server1).getFileDownloadNetworkDescription());
				}
			}else{
				filesStored = new FileStored(filename);
				filesStored.addNetworkDescriptor(this.systemWatcher.getWatchedElement(server1).getFileDownloadNetworkDescription());
				
				
			}
			mapOfFilesToHosts.put(filename, filesStored);
			
		}
		
		
		return mapOfFilesToHosts;
		
	}
	public Map<String,List> findHostsOfFileForUse(String filename){
		HashMap<String, List> mapOfFilesToHosts = new HashMap<String, List>();
		ArrayList<FileStoreNode> listOfNodesWithFile = new ArrayList<FileStoreNode>();
		Map<String,String> mapOfFilesToServerIds = this.systemWatcher.getAllFilesStored();
		
		String serverList = mapOfFilesToServerIds.get(filename);
		String[] temp = serverList.split(":");
		String server1 = temp[0]+":"+temp[1];
		FileStoreNode fileNodeServer1 = this.systemWatcher.getFileStoreNodeFromFileStore(server1);
		FileStoreNode fileNodeServer2 = null;
		listOfNodesWithFile.add(fileNodeServer1);
		String server2 = null;
		if(temp.length > 2){
			server2 = temp[2]+":"+temp[3];
		}
		if(server2 != null){
			fileNodeServer2 = this.systemWatcher.getFileStoreNodeFromFileStore(server2);
			listOfNodesWithFile.add(fileNodeServer2);
		}
		mapOfFilesToHosts.put(filename, listOfNodesWithFile);
		
		return mapOfFilesToHosts;
	}
	public Map<String, FileStored> getFilesMap(){
		return this.mapOfFiles;
	}
	public Map<String, ClientDescriptor> getClientMap(){
		return this.systemWatcher.getClientMap();
	}
	public OverWatcher getSystemWatcher(){
		return this.systemWatcher;
	}

	public void handleHeartBeatLost(String serverId) {
		// TODO Auto-generated method stub
	}

	public void dataResponse(String serverId, String data) {
		if(data.equalsIgnoreCase("DELETE-CLIENT")){
			this.systemWatcher.removeClient(serverId);
		}
		
	}
}
